Skip to content

Add mcp-bridge: use any MCP server's tools by voice - #391

Merged
uzair401 merged 5 commits into
openhome-dev:devfrom
hassan1731996:add-mcp-bridge
Nov 11, 2026
Merged

uzair401 merged 5 commits into
openhome-dev:devfrom
hassan1731996:add-mcp-bridge

Conversation

@hassan1731996

@hassan1731996 hassan1731996 commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor

What does this Ability do?

MCP Bridge connects the speaker to one MCP server the user chooses and lets them use every tool on it by voice. The user adds the server's address, and a token if it needs one, under Settings, API Keys. MCP Bridge then lists the server's tools, picks the right one for what the user says, fills in the details, and speaks back a short answer. Nothing has to be set up per tool. One Zapier MCP server alone reaches thousands of apps.

No existing ability or template uses MCP.

  • n8n Commander needs a webhook wired for each workflow.
  • The API template is one hardcoded endpoint per ability.
  • OpenClaw, Hermes, the Local Link templates and Voice Dev Terminal need software running on the user's computer.

MCP Bridge finds a hosted server's tools by itself, through the open standard. The README covers the differences.

Suggested Trigger Words

  • MCP bridge
  • use my MCP
  • ask my server
  • use my tools
  • Zapier

Type

  • New community Ability
  • Improvement to existing Ability
  • Bug fix
  • Documentation update

External APIs

  • No external APIs
  • Uses external API(s): the MCP server the user configures, over HTTPS. API keys: mcp_server_url (required) and mcp_server_token (optional, sent as Authorization: Bearer), both read with get_api_keys(). These public servers need no key and work for testing: https://docs.mcp.cloudflare.com/mcp, https://mcp.deepwiki.com/mcp.

Testing

  • Tested in OpenHome Live Editor
  • All exit paths tested (said "stop", "exit", etc.)
  • Error scenarios tested (API down, bad input, etc.)

Live Editor spike. A throwaway spike ran in the Live Editor and confirmed the sandbox supports everything this needs:

  • socket.getaddrinfo
  • requests streaming with allow_redirects=False
  • a 10-second HTTP wait inside a session
  • untagged optional API keys

The full ability has since been tested in the Live Editor and works.

Offline tests (103 scripted cases). These run against a fake MCP server in three modes: modern JSON, modern SSE, and the legacy session handshake. They cover:

  • 401s, redirects, oversized replies, session expiry, unsupported versions and invalid x-mcp-header tools
  • private and metadata addresses being refused
  • exit at every prompt, plus the confirm, change and decline paths
  • read-only versus write gating
  • a broken timezone, a corrupt Social Memory file and a failed storage read

Live checks against real servers. I also ran the transport against DeepWiki, Cloudflare Docs and Hugging Face. All three connect and their tool calls work (4 to 9 seconds). Zapier without a token gives the "needs sign-in" message.

Checklist

  • Files are in community/mcp-bridge/
  • main.py follows SDK pattern (extends MatchingCapability, has register_capability + call)
  • README.md included with description, suggested triggers, and setup
  • resume_normal_flow() called on every exit path
  • No print() — using editor_logging_handler
  • No hardcoded API keys — using placeholders
  • No blocked imports (redis, user_config)
  • No asyncio.sleep() or asyncio.create_task() — using session_tasks
  • Error handling on all external calls
  • Tested in OpenHome Live Editor

Anything else?

Protocol

  • It speaks the MCP 2026-07-28 stateless Streamable HTTP revision: one POST per call with MCP-Protocol-Version, Mcp-Method and Mcp-Name, base64 header values for non-ASCII names, and Mcp-Param-* mirroring for x-mcp-header.
  • It falls back automatically to the 2025-11-25 initialize / Mcp-Session-Id handshake, following the spec's era-detection rules, and caches which one the server uses.
  • It handles JSON and SSE replies, and follows tools/list pagination.
  • Limits, stated in the README:
    • OAuth-only servers aren't supported.
    • Servers must be public and use HTTPS.
    • Only one server at a time.

Safety

  • Addresses: https only, no redirects followed. Private, loopback, link-local and metadata addresses are refused, both as typed and after the name is resolved.
  • Size limits: replies are capped at 512 KB, and only the first 6,000 characters are summarized.
  • Untrusted text: tool descriptions and tool output go to the LLM as fenced, untrusted data. There's exactly one tool call per user request, and output never triggers another call.
  • Confirmation:
    • A tool runs straight away only when the server marks it read-only, nothing about it looks like a write, and none of its values came from the user's stored details.
    • Everything else asks "Go ahead, or change something?". The user can confirm, decline, or change the details.
  • Secrets: tokens and query strings are never logged, and no token or tool output is stored.

Following earlier review feedback

  • Triggers: no does_match(); triggers live in the dashboard.
  • Routing: an LLM router with natural examples, and an LLM confirmation classifier with a whole-utterance fast path.
  • Exits: checked at every prompt, using whole-phrase matches.
  • State: saved in Context Storage with create_key, then update_key, checking success. Reads unwrap ["value"], and a failed read blocks saves.
  • HTTP: runs through asyncio.to_thread. No gather, no PEP 604 annotations.
  • Prompts: missing details are collected in at most two prompts, as asked in Add flight-booking community ability #209.
  • Speech:
    • Plain spoken English under 30 words, with natural dates and 12-hour times. Emails and ids are never read out.
    • No tool counts in the greeting.
    • Context-aware fillers that don't repeat, and plain error lines without setup jargon.
    • The session stays open after each answer.
  • Context reuse (read-only):
    • Data from other abilities: user_profile.md, user_summary.md, memory_mirror_corrections.md (Memory Mirror) and the names in social_memory.json (Social Memory; its private snippets are skipped).
    • Platform calls: get_region_data(), and get_timezone() with a UTC fallback.
    • These fill details like "my city" or "tomorrow" instead of asking. Any value taken from them always goes through the spoken confirmation, because it's about to leave for a third-party server.
  • Marketplace: I couldn't check the Marketplace for an overlapping ability from outside a logged-in account. I'll confirm before merge.

Connects to one user-chosen MCP server (URL and optional bearer token in
Settings, API Keys) and routes spoken requests to its tools.

- Speaks MCP 2026-07-28 stateless Streamable HTTP, with automatic
  fallback to the 2025-11-25 initialize/session handshake
- JSON and SSE replies, tools/list pagination, x-mcp-header mirroring
- Read-only tools run directly; anything else is confirmed by voice
  with a chance to change the details
- https only, no redirects, private and metadata addresses refused
- Tool descriptions and output are fenced as untrusted data
- Ask for all missing fields in one prompt, with one follow-up at most
- Greeting without tool counts; spoken output capped at 30 words with
  plain-spoken-English instructions; natural dates; no emails read out
- Wider exit and yes phrase lists; natural examples in the router
- Context-aware, non-repeating fillers; plain error wording
- Read user_profile.md, user_summary.md, memory_mirror_corrections.md,
  Social Memory names, get_region_data() and get_timezone() instead of
  asking; any value taken from them always goes through the spoken
  confirmation, even for read-only tools
@hassan1731996
hassan1731996 requested a review from a team as a code owner November 8, 2026 19:51
@github-actions

github-actions Bot commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

✅ Community PR Path Check — Passed

All changed files are inside the community/ folder. Looks good!

@github-actions github-actions Bot added the community-ability Community-contributed ability label Nov 8, 2026
@github-actions

github-actions Bot commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

🔀 Branch Merge Check

PR direction: add-mcp-bridge → dev

✅ Passed — add-mcp-bridge → dev is a valid merge direction

@github-actions

github-actions Bot commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

✅ Ability Validation Passed

📋 Validating: community/mcp-bridge
  ⚠️  Found 3 classes — only one class per main.py is recommended
  ✅ All checks passed!

@github-actions

github-actions Bot commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

🔍 Lint Results

✅ __init__.py — Empty as expected

Files linted: community/mcp-bridge/main.py

✅ Flake8 — Passed

✅ All checks passed!

- Put the ability class first in main.py.
- Drop the socket and urllib imports; parse URLs with a small helper.
- Ask before sending a saved detail even when it's inside a longer value.
- Log the server host only, since some servers put the key in the path.
- Say a short filler while connecting, and read event streams as UTF-8.
@uzair401

Copy link
Copy Markdown
Contributor

Hello @hassan1731996, thanks for this, really solid work, especially on the safety side. I pushed a few fixes to this branch:

  • The ability class needs to be the first class in main.py, so the helper classes are moved below it.
  • socket and urllib aren't allowed by the push check. URLs are now parsed with a small helper, and the DNS lookup check is dropped. The typed-address checks still apply.
  • Saved details were only caught when they were the whole value, so "events near Islamabad" skipped the confirmation. It now asks whenever a saved detail appears anywhere in a value.
  • Logs now show only the server host, since some servers (like Zapier) put the key in the URL path.
  • It now says "One sec, connecting." before connecting, and reads event streams as UTF-8.

Worth a follow-up:

  • The confirmation sentence is written by the LLM from the tool's arguments. Adding the real tool name in code would make it harder to mislead.
  • Saying "cancel" or "nothing" while answering a question ends the whole session instead of skipping that step.
  • Please test it live against a public server like DeepWiki before merge.

@uzair401
uzair401 merged commit 6717a44 into openhome-dev:dev Nov 11, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community-ability Community-contributed ability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants